package emulator.assembler;

import java.util.HashMap;
import java.util.TreeMap;

public class DataDescriptionTable {
	private HashMap<Integer,Integer> address_to_index = new HashMap<Integer, Integer>();
	private HashMap<Integer,Integer> index_to_address = new HashMap<Integer, Integer>();
	private TreeMap<Integer, DataDescription> description_map = new TreeMap<Integer, DataDescription>();
	
	public DataDescriptionTable(int address_min, int address_max, int initial_granularity)
	{
		int position = 0;
		for (int i = address_min; i <= address_max; i += initial_granularity)
		{
			description_map.put(i, new DataDescription(i, initial_granularity));
			address_to_index.put(i, position);
			index_to_address.put(position, i);
			position++;
		}
	}
	
	public DataDescriptionTable(DataDescriptionTable source)
	{
		description_map.clear();
		synchronized (source) {
			for (DataDescription data : source.description_map.values())
				description_map.put(data.getAddress(), data);
		}
		reindex();
	}
	
	public synchronized void reindex()
	{
		address_to_index.clear();
		index_to_address.clear();
		int position = 0;
		for (Integer address : description_map.keySet())
		{
			address_to_index.put(address, position);
			index_to_address.put(position, address);
			position++;
		}
	}
	
	public synchronized DataDescription getDescriptionForAddress(int address)
	{
		return description_map.floorEntry(address).getValue();
	}

	public synchronized DataDescription getDescriptionForIndex(int index)
	{
		Integer address = index_to_address.get(index);
		return (address == null ? null : description_map.get(address));
	}

	/**
	 * @return
	 * @see java.util.TreeMap#size()
	 */
	public synchronized int size() {
		return description_map.size();
	}

	public synchronized int getIndexForAddress(int address) {
		int key_address = description_map.floorKey(address);
		return address_to_index.get(key_address);
	}

	public synchronized void insertInstruction(int address, Instruction instruction) {
		DataDescription description = getDescriptionForAddress(address);
		if (description.getAddress() < address)
		{
			int prefix = address - description.getAddress();
			int block_end = instruction.getByteCount() + prefix;
			while (description.getLength() < block_end)
				growEntry(description);
			splitEntry(description, prefix);
		}
			
		int block_size = instruction.getByteCount();
		while (description.getLength() < block_size)
			growEntry(description);
		if (description.getLength() == block_size)
			description.setIsCode(true);
		else
		{
			shiftEntryBegin(description, block_size);
			insertCodeBlock(address, block_size);
		}
	}

	public void splitEntry(DataDescription description, int prefix) {
		DataDescription description2 = new DataDescription(description.getAddress(), prefix);
		description2.setIsCode(description.isCode());
		shiftEntryBegin(description, prefix);
		description_map.put(description2.getAddress(), description2);
	}

	public void growEntry(DataDescription description) {
		DataDescription next = description_map.higherEntry(description.getAddress()).getValue();
		description_map.remove(next.getAddress());
		description.setLength(description.getLength()+next.getLength());
	}

	public void insertCodeBlock(int address, int block_size)
	{
		DataDescription new_data = new DataDescription(address, block_size);
		new_data.setIsCode(true);
		description_map.put(address, new_data);
	}

	public void shiftEntryBegin(DataDescription description, int front_reduce)
	{
		description_map.remove(description.getAddress());
		description.setAddress(description.getAddress() + front_reduce);
		description.setLength(description.getLength() - front_reduce);
		description_map.put(description.getAddress(), description);
	}
}
